package cn.ds.assets.app;

import cn.ds.assets.framework.common.util.collection.CollectionUtils;
import cn.ds.assets.module.infra.controller.admin.codegen.vo.CodegenCreateListReqVO;
import cn.ds.assets.module.infra.dal.mysql.codegen.CodegenColumnMapper;
import cn.ds.assets.module.infra.dal.mysql.codegen.CodegenTableMapper;
import cn.ds.assets.module.infra.enums.codegen.CodegenSceneEnum;
import cn.ds.assets.module.infra.framework.codegen.config.CodegenProperties;
import cn.ds.assets.module.infra.service.codegen.inner.CodegenBuilder;
import cn.ds.assets.module.infra.service.db.DatabaseTableServiceImpl;
import cn.hutool.core.collection.CollUtil;
import cn.ds.assets.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.ds.assets.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.ds.assets.module.infra.service.codegen.inner.CodegenEngine;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;

import static cn.ds.assets.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.ds.assets.module.infra.enums.ErrorCodeConstants.*;

/**
 * 代码生成工具类
 */
@Service
@Slf4j
public class CodegenTool {

    @Resource
    private CodegenTableMapper codegenTableMapper;

    @Resource
    private CodegenColumnMapper codegenColumnMapper;

    @Resource
    private CodegenEngine codegenEngine;

    @Resource
    private DatabaseTableServiceImpl databaseTableService;

    @Resource
    private CodegenBuilder codegenBuilder;

    @Resource
    private CodegenProperties codegenProperties;

    public Map<String, String> generationCodes(List<Long> tableIds) {
        return this.generationCodes(tableIds,null);
    }

    public Map<String, String> generationCodes(List<Long> tableIds,String modleName) {
        List<CodegenTableDO> list = codegenTableMapper.selectList(new LambdaQueryWrapper<CodegenTableDO>().in(CodegenTableDO::getId,tableIds));
        Map<String, String>  allMap = new HashMap<>();

        list.forEach(dto->{
            // 校验是否已经存在
            CodegenTableDO table = codegenTableMapper.selectById(dto.getId());
            if (table == null) {
                throw exception(CODEGEN_TABLE_NOT_EXISTS);
            }
            List<CodegenColumnDO> columns = codegenColumnMapper.selectListByTableId(table.getId());
            if (CollUtil.isEmpty(columns)) {
                throw exception(CODEGEN_COLUMN_NOT_EXISTS);
            }
            if(ObjectUtil.isEmpty(modleName)){
                table.setClassName("Base"+table.getClassName());
            }else{
                table.setClassName(table.getClassName());
                table.setIsApp(true);//该模块为服务模块
            }
            Map<String, String>  map = codegenEngine.execute(table, columns);
            allMap.putAll(map);
        });
        //数据资产模块使用默认的处理路径
        if(ObjectUtil.isEmpty(modleName)){
            return dealPath(allMap);
        }
        //其他模块使用另外的处理路径
        return dealAppPath(allMap,modleName);
    }

    /**
     * 处理数据资产模块相关
     * @param allMap
     * @return
     */
    private Map<String, String> dealPath(Map<String, String>  allMap){
        Map<String, String>  rsMap = new HashMap<>();
        String backDir = "assets-module-assets/assets-module-assets-biz/src/main/java/";
        //String backDir = "/src/main/java/";
        allMap.forEach((key,value)->{
            String indexDir = backDir;
            if(key.indexOf(indexDir)>-1){
                String k = key.replace(indexDir,"");
                rsMap.put(k,value);
            }
            if(key.equals("sql/sql.sql")){
                String k = "cn/ds/assets/module/assets/sql/sql.sql";
                rsMap.put(k,value);
            }
        });
        return rsMap;
    }

    /**
     * 修改文件路径
     * @param allMap
     * @param modleName 模块名称
     * @return
     */
    private Map<String, String> dealAppPath(Map<String, String>  allMap,String modleName){
        Map<String, String>  rsMap = new HashMap<>();
        String backDir = "assets-module-"+modleName+"/assets-module-"+modleName+"-biz/src/main/java/";
        allMap.forEach((key,value)->{
            String indexDir = backDir;
            if(key.indexOf(indexDir)>-1){
                String k = key.replace(indexDir,"");
                k = k.replace("/"+modleName+"/","/app/");//替换模块路径为app
                rsMap.put(k,value);
            }
            if(key.equals("sql/sql.sql")){
                String k = "cn/ds/assets/module/sql/sql.sql";
                rsMap.put(k,value);
            }
        });
        return rsMap;
    }

    /**
     * 创建需要自动生成代码的表
     * @return
     */
    public List<Long> createCodegenAll(String prefixTableName) {
        List<Long> ids = new ArrayList<>();

        CodegenCreateListReqVO reqVO = new CodegenCreateListReqVO();
        reqVO.setDataSourceConfigId(0L);

        List<TableInfo> list = databaseTableService.getTableList(reqVO.getDataSourceConfigId(),prefixTableName,null);
        reqVO.setTableNames(list.stream().map(TableInfo::getName).collect(Collectors.toList()));

        // 遍历添加。虽然效率会低一点，但是没必要做成完全批量，因为不会这么大量
        reqVO.getTableNames().forEach(tableName -> ids.add(createCodegen(reqVO.getDataSourceConfigId(), tableName)));

        //执行表同步
        ids.forEach(id -> syncCodegenFromDB(id));

        log.info("完成所有"+prefixTableName+"开头的表添加到自定生成代码表中");

        return ids;
    }

    public Long createCodegen(Long dataSourceConfigId, String tableName) {
        // 从数据库中，获得数据库表结构
        TableInfo tableInfo = databaseTableService.getTable(dataSourceConfigId, tableName);
        // 导入
        return createCodegen0(dataSourceConfigId, tableInfo);
    }

    private Long createCodegen0(Long dataSourceConfigId, TableInfo tableInfo) {
        // 校验导入的表和字段非空
        validateTableInfo(tableInfo);

        List<CodegenTableDO> tables = codegenTableMapper.selectList();

        Optional<CodegenTableDO> op = tables.stream().filter(t->tableInfo.getName().equals(t.getTableName())&& !t.getDeleted()).findFirst();
        if (op.isPresent()) {
            return op.get().getId();
        }

        // 构建 CodegenTableDO 对象，插入到 DB 中
        CodegenTableDO table = codegenBuilder.buildTable(tableInfo);
        table.setDataSourceConfigId(dataSourceConfigId);
        table.setScene(CodegenSceneEnum.ADMIN.getScene()); // 默认配置下，使用管理后台的模板
        table.setFrontType(codegenProperties.getFrontType());
        table.setAuthor("admin");
        codegenTableMapper.insert(table);

        // 构建 CodegenColumnDO 数组，插入到 DB 中
        List<CodegenColumnDO> columns = codegenBuilder.buildColumns(table.getId(), tableInfo.getFields());
        // 如果没有主键，则使用第一个字段作为主键
        if (!tableInfo.isHavePrimaryKey()) {
            columns.get(0).setPrimaryKey(true);
        }
        codegenColumnMapper.insertBatch(columns);
        return table.getId();
    }

    private void validateTableInfo(TableInfo tableInfo) {
        if (tableInfo == null) {
            throw exception(CODEGEN_IMPORT_TABLE_NULL);
        }
        if (StrUtil.isEmpty(tableInfo.getComment())) {
            throw exception(CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL);
        }
        if (CollUtil.isEmpty(tableInfo.getFields())) {
            throw exception(CODEGEN_IMPORT_COLUMNS_NULL);
        }
        tableInfo.getFields().forEach(field -> {
            if (StrUtil.isEmpty(field.getComment())) {
                throw exception(CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL, field.getName());
            }
        });
    }

    public void syncCodegenFromDB(Long tableId) {
        // 校验是否已经存在
        CodegenTableDO table = codegenTableMapper.selectById(tableId);
        if (table == null) {
            throw exception(CODEGEN_TABLE_NOT_EXISTS);
        }
        // 从数据库中，获得数据库表结构
        TableInfo tableInfo = databaseTableService.getTable(table.getDataSourceConfigId(), table.getTableName());
        // 执行同步
        syncCodegen0(tableId, tableInfo);
    }

    private void syncCodegen0(Long tableId, TableInfo tableInfo) {
        // 校验导入的表和字段非空
        validateTableInfo(tableInfo);
        List<TableField> tableFields = tableInfo.getFields();

        // 构建 CodegenColumnDO 数组，只同步新增的字段
        List<CodegenColumnDO> codegenColumns = codegenColumnMapper.selectListByTableId(tableId);
        Set<String> codegenColumnNames = CollectionUtils.convertSet(codegenColumns, CodegenColumnDO::getColumnName);

        //计算需要修改的字段，插入时重新插入，删除时将原来的删除
        BiPredicate<TableField, CodegenColumnDO> pr =
                (tableField, codegenColumn) -> tableField.getMetaInfo().getJdbcType().name().equals(codegenColumn.getDataType())
                        && tableField.getMetaInfo().isNullable() == codegenColumn.getNullable()
                        && tableField.isKeyFlag() == codegenColumn.getPrimaryKey()
                        && tableField.getComment().equals(codegenColumn.getColumnComment());
        Map<String, CodegenColumnDO> codegenColumnDOMap = CollectionUtils.convertMap(codegenColumns, CodegenColumnDO::getColumnName);
        //需要修改的字段
        Set<String> modifyFieldNames = tableFields.stream()
                .filter(tableField -> codegenColumnDOMap.get(tableField.getColumnName()) != null
                        && !pr.test(tableField, codegenColumnDOMap.get(tableField.getColumnName())))
                .map(TableField::getColumnName)
                .collect(Collectors.toSet());
        // 计算需要删除的字段
        Set<String> tableFieldNames = CollectionUtils.convertSet(tableFields, TableField::getName);
        Set<Long> deleteColumnIds = codegenColumns.stream()
                .filter(column -> (!tableFieldNames.contains(column.getColumnName())) || modifyFieldNames.contains(column.getColumnName()))
                .map(CodegenColumnDO::getId).collect(Collectors.toSet());
        // 移除已经存在的字段
        tableFields.removeIf(column -> codegenColumnNames.contains(column.getColumnName()) && (!modifyFieldNames.contains(column.getColumnName())));
        if (CollUtil.isEmpty(tableFields) && CollUtil.isEmpty(deleteColumnIds)) {
            return;//throw exception(CODEGEN_SYNC_NONE_CHANGE);
        }

        // 插入新增的字段
        List<CodegenColumnDO> columns = codegenBuilder.buildColumns(tableId, tableFields);
        codegenColumnMapper.insertBatch(columns);
        // 删除不存在的字段
        if (CollUtil.isNotEmpty(deleteColumnIds)) {
            codegenColumnMapper.deleteBatchIds(deleteColumnIds);
        }
    }


}
